package com.zzyy.study.aop;

import com.zzyy.study.annotation.RequestKeyParam;
import com.zzyy.study.annotation.RequestLock;
import com.zzyy.study.exception.BizException;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * @auther zzyy
 * @create 2024-03-13 11:50
 */
@Aspect
@Component
@Slf4j
public class RedisRequestLockAspect
{
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     *
     * @param joinPoint 连接点(joinpoint)
     * 就是那些被拦截到的点，就是方法。也就是主线业务中的主要方法，每一
     * 方法就是一个连接点，比如UserServiceImpl类的CRUD四个方法，就是4个连接点。
     * @return
     */
    @Around("execution(public * * (..)) && @annotation(com.zzyy.study.annotation.RequestLock)")
    public Object interceptor(ProceedingJoinPoint joinPoint)
    {
        Object result = null;

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        RequestLock requestLock = method.getAnnotation(RequestLock.class);

        if (StringUtils.isEmpty(requestLock.prefix()))
        {
            throw new BizException("BIZ-0001", "@RequestLock注解prefix字段不能为空");
        }
        //获取自定义key,prefix()+添加了RequestKeyParam注解的Field字段，拼接起来
        final String lockKey = getLockKey(joinPoint);

        System.out.println("*******lockKey: "+lockKey);

        // 使用RedisCallback接口执行set命令，设置锁键；设置过期时间和SET_IF_ABSENT选项
        final Boolean success = stringRedisTemplate.execute(
                (RedisCallback<Boolean>) connection -> connection.set(lockKey.getBytes(), "repeate".getBytes(),
                        Expiration.from(requestLock.expire(), requestLock.timeUnit()),
                        RedisStringCommands.SetOption.ifAbsent())); //setnx

        if (!success)
        {
            throw new BizException("BIZ-0002", "您的操作太快了,请稍后重试");
        }

        try
        {
            result = joinPoint.proceed();//放行
        } catch (Throwable throwable) {
            throw new BizException("SYS-0001", "系统异常");
        }

        return result;
    }

    /**
     * 唯一key业务规则获取 ，
     * @param joinPoint
     * @return
     */
    public String getLockKey(ProceedingJoinPoint joinPoint)
    {
        StringBuilder stringBuilder = new StringBuilder();

        //获取连接点的方法签名对象
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        //Method对象
        Method method = methodSignature.getMethod();
        System.out.println("******method: "+method);
        System.out.println("******method.getName(): "+method.getName());
        //获取Method对象上的注解对象
        RequestLock requestLock = method.getAnnotation(RequestLock.class);
        //获取方法参数
        final Object[] args = joinPoint.getArgs();
        for (Object element : args) {
            System.out.println("******args: "+element);
        }
        //获取Method对象上所有的注解
        final Parameter[] parameters = method.getParameters();
        System.out.println(parameters.length);
        for (Parameter element : parameters) {
            System.out.println("******parameters: "+element.getName());
        }


        for (int i = 0; i < parameters.length; i++)
        {
            final RequestKeyParam keyParam = parameters[i].getAnnotation(RequestKeyParam.class);
            //如果属性不是RequestKeyParam注解，则不处理
            if (keyParam == null) {
                continue;
            }
            //如果属性是RequestKeyParam注解，则拼接 连接符 "& + RequestKeyParam"
            stringBuilder.append(requestLock.delimiter()).append(args[i]);
        }
        System.out.println("******stringBuilder拼接有RequestKeyParam注解: "+stringBuilder);

        //注解@RequestKeyParam可以放在方法的参数上，
        // 也可以放在对象的属性上，所以这里需要进行两次判断，一次是获取方法上的注解，一次是获取对象里面属性上的注解。

        //如果方法上没有加RequestKeyParam注解
        if (StringUtils.isEmpty(stringBuilder.toString())) {
            //获取方法上的多个注解（为什么是两层数组：因为第二层数组是只有一个元素的数组）
            final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            //循环注解
            for (int i = 0; i < parameterAnnotations.length; i++) {
                final Object object = args[i];
                //获取注解类中所有的属性字段
                final Field[] fields = object.getClass().getDeclaredFields();
                for (Field field : fields) {
                    //判断字段上是否有RequestKeyParam注解
                    final RequestKeyParam annotation = field.getAnnotation(RequestKeyParam.class);
                    //如果没有，跳过
                    if (annotation == null) {
                        continue;
                    }
                    //如果有，设置Accessible为true（为true时可以使用反射访问私有变量，否则不能访问私有变量）
                    field.setAccessible(true);
                    //如果属性是RequestKeyParam注解，则拼接 连接符" & + RequestKeyParam"
                    stringBuilder.append(requestLock.delimiter()).append(ReflectionUtils.getField(field, object));
                }
            }
            System.out.println("******stringBuilder拼接有RequestKeyParam注解: "+stringBuilder);
        }

        //返回指定前缀的key，*******lockKey: addUser&许攸3&13811110003
        return requestLock.prefix() + stringBuilder;
    }
}
